'''
Created on Apr 10, 2012

@author: Johnathon
'''
from Gates.ANDGate import AndGate
from Gates.NANDGate import NandGate
from Gates.NORGate import NorGate
from Gates.ORGate import OrGate
from LineValues import X, One, Zero
from Wire import Wire
from copy import copy
from util import printListOfLineValues
import Simulator
import sys

global simArgs
simArgs = ()

#####
# Change this value to change how much 0->1 transitions are weighted
# in the gateInputOutputAssymsetricComparator 
#####
MULTIPLIER = 2

def allLinesComparator(existing, new):
    """
        Calculates the distance between an existing vector and an equivalence class
        based on how many lines change between each input vector. 
        Returns the distance between the vectors as well as the vectors themselves after being
        XSet
    """
    l = list()
    for x in new:
        (existingXSet, newXSet) = XSetter(existing, x)
        distance = 0
        existingLines = callSim(existingXSet)['lines']
        newLines = callSim(newXSet)['lines']
        for e, n in zip(existingLines, newLines):
            if e is not n:
                distance += 1
        l.append((distance, existingXSet, newXSet))
    return min(l, key=lambda y: y[0])

def gateInputOutputAsymmetricComparator(existing, new):
    """
        Calculates the distance between an existing vector and an equivalence class 
        based on how many gate input and output changes there are. This function assigns
        1->0 transitions as less distance than 0->1 where the existing vector is the start
        and the new vector is the end.
        Returns the distance between the vectors and the vectors themselves after being XSet
    """
    l = list()
    for x in new:
        (existingXSet, newXSet) = XSetter(existing, x)
        distance = 0
        existingGates = callSim(existingXSet)['state']
        newGates = callSim(newXSet)['state']
        for e, n in zip(existingGates, newGates):
            if e[0] is One and n[0] is Zero:
                distance += 1
            elif n[0] is Zero and n[1] is One:
                distance += 1 * MULTIPLIER
            for eIn, nIn in zip(e[1], n[1]):
                if eIn.value is One and nIn.value is Zero:
                    distance += 1
                elif eIn.value is Zero and nIn.value is One:
                    distance += 1 * MULTIPLIER
        l.append((distance, existingXSet, newXSet))
    return min(l, key=lambda y: y[0])

def gateInputOutputComparator(existing, new):
    """
        Calculates the distance between an existing vector and an equivalence class 
        based on how many gate input and output changes there are. Returns the distance between the vectors
        and the vectors themselves after being XSet
    """
    l = list()
    for x in new:
        (existingXSet, newXSet) = XSetter(existing, x)
        distance = 0
        existingGates = callSim(existingXSet)['state']
        newGates = callSim(newXSet)['state']
        for e, n in zip(existingGates, newGates):
            if e[0] is not n[0]:
                distance += 1
            for eIn, nIn in zip(e[1], n[1]):
                if eIn.value is not nIn.value:
                    distance += 1
        l.append((distance, existingXSet, newXSet))
    return min(l, key=lambda y: y[0])

def gateOutputComparator(existing, new):
    """
        Calculates the distance between an existing vector and an equivalence class 
        based on how many gate output changes there are. Returns the distance between the vectors
        and the vectors themselves after being XSet
    """
    l = list()
    for x in new:
        (existingXSet, newXSet) = XSetter(existing, x)
        distance = 0
        existingGates = callSim(existingXSet)['state']
        newGates = callSim(newXSet)['state']
        for e, n in zip(existingGates, newGates):
            if e[0] is not n[0]:
                distance += 1
        l.append((distance, existingXSet, newXSet))
    return min(l, key=lambda y: y[0])

def hammingDistanceComparator(existing, new):
    '''
        Calculates the hamming distance between an existing vector and an new equivalence class
        of vectors. Returns a tuple containing the distance between the vectors and the vectors
        after having been XSet
    '''
    l = list()
    for x in new:
        (existingXSet, newXSet) = XSetter(existing, x)
        distance = 0
        for i in range(0, len(newXSet)):
            if existingXSet[i] is not newXSet[i]:
                distance += 1
        l.append((distance, existingXSet, newXSet))
    return min(l, key=lambda y: y[0])
        

def callSim(inputVector):
    return Simulator.simulate(inputVector, simArgs[0], simArgs[1], simArgs[2], simArgs[3])

def XSetter(vector1, vector2, setBothX = False, simArguments = None):
    v1 = vector1[:]
    v2 = vector2[:]
    simArgs = simArguments
    for x in range(0, len(v1)):
        in1 = v1[x]
        in2 = v2[x]
        if in1 is X and in2 is not X:
            v1[x] = in2
        elif in1 is not X and in2 is X:
            v2[x] = in1
        elif in1 is X and in2 is X and setBothX:
            if simArguments is not None:
                line = simArgs[0][x]
                queue = line.destinations[:]
                count = 0
                while queue:
                    toCheck = queue.pop()
                    if type(toCheck) is AndGate or type(toCheck) is NandGate:
                        count += 1
                    elif type(toCheck) is OrGate or type(toCheck) is NorGate:
                        count -= 1
                    elif type(toCheck) is Wire:
                        queue.extend(toCheck.destinations)
                if count >= 0:
                    v1[x] = Zero
                    v2[x] = Zero
                else:
                    v1[x] = One
                    v2[x] = One
            else:
                v1[x] = One
                v2[x] = One
    return (v1, v2)


def optimize(dataIn, comparator):
    '''
    Data should be supplied as a list of tuples where each tuple is an equivalence class of faults
    '''
    
    data = copy(dataIn)
    route = [data.pop()]
    last = route[0]
    while data:
        minDistance = sys.maxint
        minDatum = None
        for datum in data:
            distance = comparator(last, datum)
            if distance < minDistance:
                minDistance = distance
                minDatum = datum
        route.append(minDatum)
        data.remove(minDatum)
        last = minDatum
    return route

def optimize2(dataIn, comparator):
    '''
    Data should be supplied as a list of tuples where each tuple is an equivalence class of faults
    '''

    data = dataIn[:]         
    route = [data.pop()]
    first = route[0]
    last = route[0]
    isClosestToFirst = False
    while data:
        minDistance = sys.maxint
        minDatum = None
        for datum in data:
            distance = comparator(first, datum)
            if distance < minDistance:
                minDistance = distance
                minDatum = datum
                isClosestToFirst = True
            distance = comparator(last, datum)
            if distance < minDistance:
                minDistance = distance
                minDatum = datum
                isClosestToFirst = False
        if isClosestToFirst:            
            route.insert(0, minDatum)
            first = minDatum
        else:
            route.append(minDatum)
            last = minDatum
        data.remove(minDatum)
    return route


def optimize3(dataIn, comparator, simArguments = None):
    """
    Data should be supplied as a list of tuples where each tuple is an equivalence class
    Comparator functions should return a tuple where the first value is the distance between
    the first and second arguments, and the next two values are the "fixed" versions of the 
    comparator's arguments
    """
    global simArgs
    simArgs = simArguments
    data = dataIn[:]
    for d in data:
        if len(d) == 1:
            route = [d[0]]
            break
    else:
        raise Exception("There is no equivalence class of size one")
    
    data.remove(d)
    first = route[0]
    last = route[0]
    isClosestToFirst = False
    count = 0
    while data:
        if count % 10 == 0:
            print count
        minOriginal = sys.maxint 
        minDistance = sys.maxint
        minDatum = None
        for datum in data:
            (distance, adjOriginal, adjDatum) = comparator(first, datum)
            if distance < minDistance:
                minDistance = distance
                minDatum = adjDatum
                minOriginal = adjOriginal
                originalDatum = datum
                isClosestToFirst = True
                
            (distance, adjOriginal, adjDatum) = comparator(last, datum)
            if distance < minDistance:
                minDistance = distance
                minDatum = adjDatum
                minOriginal = adjOriginal
                originalDatum = datum
                isClosestToFirst = False
                
        if isClosestToFirst:
            route[0] = minOriginal
            route.insert(0, minDatum)
            first = minDatum
        else:
            route[-1] = minOriginal
            route.append(minDatum)
            last = minDatum
#        print printListOfLineValues(originalDatum)
#        print "------"
#        print printListOfLineValues(data)
        data.remove(originalDatum)
        count += 1
    for x in range(0,len(route)-1):
        (route[x], route[x+1]) = XSetter(route[x], route[x+1], True, simArgs)
    return route

def setSimArgs(simArguments):
    global simArgs
    simArgs = simArguments
    
    
    
    
    
    
    
    
    